/* ================================================================================================================== */
/* COPYRIGHT SOLUTIUM 2005-2006
/* Unlimited rights to use, modify, redistribute and resell granted to Nokia Corp.
/* ================================================================================================================== */
/**
 * Property & Copyright 2009 Nokia Siemens Networks.
 */ 
package com.nsn.das.log;

import org.apache.log4j.Logger;
import org.apache.log4j.LogManager;

import java.util.Arrays;

/**
 * Subclass of Log4j's Logger adding support for two new styles log messages: a simple formatted vararg style
 * log.info("Key %s Value %s", foo, bar) where each occurance of %s is replaced with corresponding vararg object, and
 * for fully formatted style: log.infoFormatted(...) which is parsed through Java's Formatter class.
 *
 * @author Henkka Karapuu
 */
@SuppressWarnings({"InfiniteRecursion"})
public class AdvancedLogger extends Logger {

	private static AdvancedLoggerFactory factory = new AdvancedLoggerFactory();

	//******************************************************************************************************************
	//** Instantiation
	//******************************************************************************************************************

	/**
	 * Package local default constructor - Instances should be created only through AdvancedLoggerFactory
	 */
	AdvancedLogger(String name) {
		super(name);
	}

	/**
	 * Returns AdvancedLogger for the Category specified by Class
	 */
	public static AdvancedLogger getAdvancedLogger(Class clazz) {
		Logger logger = LogManager.getLogger(clazz.getCanonicalName(), factory);
		return (AdvancedLogger) logger;
	}

	/**
	 * Returns AdvancedLogger for the Category specified by name
	 */
	public static AdvancedLogger getAdvancedLogger(String name) {
		return (AdvancedLogger) LogManager.getLogger(name, factory);
	}

	//******************************************************************************************************************
	//** Simple style logging methods
	//******************************************************************************************************************

	public void debug(String format, Object... args) {
		if( !isDebugEnabled() ) {
			return;
		}
		debug(fastReplace(format, '%', 's', args));
	}

	public void info(String format, Object... args) {
		if( !isInfoEnabled() ) {
			return;
		}
		info(fastReplace(format, '%', 's', args));
	}

	public void warn(String format, Object... args) {
		warn(fastReplace(format, '%', 's', args));
	}

	public void error(String format, Object... args) {
		error(fastReplace(format, '%', 's', args));
	}

	public void fatal(String format, Object... args) {
		fatal(fastReplace(format, '%', 's', args));
	}

	//******************************************************************************************************************
	//** Full style logging methods
	//******************************************************************************************************************

	public void debugFormatted(String format, Object... args) {
		if( !isDebugEnabled() ) {
			return;
		}
		debug(String.format(format, args));
	}

	public void infoFormatted(String format, Object... args) {
		if( !isInfoEnabled() ) {
			return;
		}
		info(String.format(format, args));
	}

	public void warnFormatted(String format, Object... args) {
		warn(String.format(format, args));
	}

	public void errorFormatted(String format, Object... args) {
		error(String.format(format, args));
	}

	public void fatalFormatted(String format, Object... args) {
		fatal(String.format(format, args));
	}

	//******************************************************************************************************************
	//** Helpers
	//******************************************************************************************************************

	/**
	 * Replaces the occurances of given two character sequences by the toString values of corresponding to string values.
	 * <p/>
	 * Example: fastReplace("Key: %s Value: %s",'%','s',"foo","bar") will return "Key: foo Value: bar".
	 *
	 * @param str		   the formatting string
	 * @param replaceChar1 required 1st char of replacement sequence
	 * @param replaceChar2 required 2nd char of replacement sequence
	 * @param args		   vararg Objects which will be inserted in order where replaceChar1 and replaceChar2 occur
	 *                     sequentally
	 * @return 			   the formatted string
	 */
	public static String fastReplace(String str, char replaceChar1, char replaceChar2, Object... args) {

		// If no arguments are given there is nothing to replace..
		if( args == null ) {
			return str;
		}

		// Setup
		StringBuffer retVal = new StringBuffer();
		int length = str.length();
		char[] buf = new char[length];
		char prev = '\u0000';
		str.getChars(0, length, buf, 0);
		int match = 0;

		// Iterate through each char of the given string, replacing occurances of the replaceChars with the args objects
		for( int i = 0; i < length; i++ ) {

			// A match is found
			if( prev == replaceChar1 && buf[i] == replaceChar2 ) {

				// Append the corresponding argument to return value
				append(retVal, args[match]);
				match++;

				// Normally after match is found and replacement appendend we skip the original %s stuff
				if( i < length - 2 ) {
					i++;
				}
				// However if there is only one char left after match the loop will terminate, so we append last char here
				else if( i < length - 1 ) {
					i++;
					retVal.append(buf[i]);
				}
				// If this was the last char we terminate the loop immediately
				else {
					break;
				}

			}

			// Otherwise just append the char
			else if( i > 0 ) {
				// todo change to use another char[] and flush when match is found
				retVal.append(prev);
				// when the loop ends in a non-match we need to append the last char here
				if( i == length - 1 ) {
					retVal.append(buf[i]);
				}
			}
			prev = buf[i];
		}

		// todo add check that all args have been used
		return retVal.toString();

	}

	private static void append(StringBuffer retVal, Object arg) {

		// The Arg is null, special replacing with <null>
		if( arg == null ) {
			retVal.append("<null>");
		}
		// The Arg is array needs also special to String conversion
		else if( arg.getClass().isArray() ) {
			Object[] objectArr = (Object[]) arg;
			retVal.append(Arrays.toString(objectArr));
		}
		// Other go by default toString
		else {
			retVal.append(arg);
		}
	}

}
